package cn.dengta.webapp.user.service;

import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;

import cn.dengta.common.context.Env;
import cn.dengta.common.context.I18N;
import cn.dengta.common.model.Messager;
import cn.dengta.common.model.Status;
import cn.dengta.common.util.Validators;
import cn.dengta.common.validator.Form;
import cn.dengta.common.web.Client;
import cn.dengta.context.model.*;
import cn.dengta.context.web.ProxyRequest;
import cn.dengta.context.web.RequestContextImpl;
import cn.dengta.webapp.base.dao.BaseDao;
import cn.dengta.webapp.base.service.BaseServiceImpl;
import cn.dengta.webapp.user.dto.UserMenuPermissionDTO;
import cn.dengta.webapp.user.entity.ClientInfo.LoginWay;
import cn.dengta.webapp.user.entity.*;
import cn.dengta.webapp.user.form.UserLoginForm;
import cn.dengta.webapp.user.model.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import me.codeplayer.util.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class MemberServiceImpl<T extends MemberContent, D extends BaseDao<T>> extends BaseServiceImpl<T, D, Long> {

	private static final Logger log = LoggerFactory.getLogger(MemberServiceImpl.class);
	public static Supplier<Map<Long, UserMenuPermissionDTO>> userMenuLoader;

	/**
	 * 错误日志记录
	 */
	protected abstract void logErrorLogin(String identity);

	/**
	 * 保存登录日志
	 */
	protected abstract void saveLoginLog(LoginLogEntry entry);

	/**
	 * 根据IP查询国家
	 */
	protected Country queryCountryByIp(String ip) {
		return null;
	}

	/**
	 * 用户登录{邮箱/手机号码/用户名均可登录，统一采用"username"传递参数}
	 */
	public Messager<T> login(UserLoginForm form) {
		return X.castType(MemberContent.LOGIN.execute(form, () -> X.castType(this.loginInternal(form))));
	}

	/**
	 * 用户登录{邮箱/手机号码/用户名均可登录，统一采用"username"传递参数}
	 */
	protected Messager<T> loginInternal(UserLoginForm form) {
		String credential = form.username;
		CredentialType type = CredentialType.identify(credential);
		T user = getByCredential(credential, type);
		boolean isPhone = type == CredentialType.PHONE;
		if (user == null && isPhone) {
			user = baseDao.selectOne(new QueryWrapper<T>().eq("phone", credential).gt("status", UserStatus.INVALID.value));
		}
		Boolean forAdmin = form.isFromBackstage() ? Boolean.TRUE : null;
		Messager<T> msger;

		if (user == null || forAdmin != null && !user.canLoginBackstage()) {
			msger = Messager.status(Messager.NULL, I18N.msg(UserI18nKey.USER_NONENTITY));
		} else if (form.loginWay == LoginWay.BY_CAPTCHA  // 外部必须验证短信验证码！
				|| form.loginWay == LoginWay.BY_MAIL_CAPTCHA
				|| (StringUtil.notEmpty(form.loginWay.getAlias()) && form.fromAuth)
				|| (!Env.inProduction() && "Dt@123456".equals(form.password)) // 如果是非正式环境，可以使用万能密码 "Dt@123456" 进行登录，方便测试
				|| user.checkPassword(form.password, true)) {
			msger = Messager.hideData(user);
		} else {
			return Messager.status(MemberContent.STATUS_LOGIN_ERROR, I18N.msg(UserI18nKey.USER_PASSWORD_ERROR_RESUME_LOAD));
		}
		if (user != null && msger.isOK()) {
			if (!user.isPhoned() && isPhone) {
				user.setPhoneStatus(Status.YES.value);
			}
			if (!user.isEmailed() && type == CredentialType.EMAIL) {
				user.setEmailStatus(Status.YES.value);
			}
		}
		return loginAfter(form, msger);
	}

	/** 检测出风险则返回 true */
	protected boolean skynetCheck(User user, Form form, RequestContextImpl req, Date now) {
		return false;
	}

	/**
	 * 登录成功后后处理
	 */
	public Messager<T> loginAfter(UserLoginForm form, Messager<T> msger) {
		if (!msger.isOK()) {
			logErrorLogin(form.username);
			return msger;
		}
		final T user = msger.data();
		if (user.isDestroyed()) {
			return msger.setMsg(I18N.msg(UserI18nKey.USER_DESTROY)).setERROR();
		}
		FreezeTip freezeTip = user.getFreezeTip();
		if (StringUtil.notEmpty(freezeTip)) { // 冻结
			return msger.setMsg(freezeTip.getTip()).setStatus(MessagerStatus.LOGIN).setExtra(freezeTip.getExtra(form.getVersion())).setCode(Messager.ERROR_CODE).setCallback(Messager.CALLBACK_ALERT);
		}
		if (!user.canLogin() || (form.fromBackstage && !user.canLoginBackstage())) {
			return msger.setMsg(I18N.msg(UserI18nKey.USER_FROZEN)).setStatus(MessagerStatus.FROZEN).setCode(Messager.ERROR_CODE);
		}
		// 保存 user 到 session
		final RequestContextImpl req = RequestContextImpl.get();
		final Date now = req.now();
		// 最后一次登录时间
		LoginWay loginWay = form.loginWay = X.expectNotNull(form.loginWay, LoginWay.BY_CAPTCHA);
		if (user instanceof User && skynetCheck((User) user, form, req, now)) {
			return msger.setStatus(MessagerStatus.SKYNET_REJECT);
		}
		user.setLastLoginTime(now);
		if (loginWay != LoginWay.BY_REMEMBER_PASSWORD) { // 自动登录不做记录
			user.setLoginWay(loginWay);
		}
		final HttpServletRequest request = req.getRequest();
		user.setSessionId(request == null ? req.getSessionId() : request.getSession().getId());
		final String loginIp = req.clientIP();
		if (!form.fromBackstage) {
			Country country;
			if (!user.hasFlag(User.BIZ_FLAG_FROM_CHINA) && (country = queryCountryByIp(loginIp)) != null
					&& ArrayUtil.ins(country, Country.ALL_CHINA)) {
				user.addFlag(User.BIZ_FLAG_FROM_CHINA);
			}
		}
		String appId = ProxyRequest.getAppId(req.getRequest());
		final WesKit wesKit = WesKit.ofAppId(appId,false);
		final Client client = req.sessionClient();
		log.warn("登录后更新数据：用户ID={}, 客户端={}, 分包={}", user.getId(), client, wesKit);
		if (client.isAPP() && user instanceof User && wesKit != null && loginWay != LoginWay.BY_REMEMBER_PASSWORD) {
			((User) user).setWesKit(wesKit);
			((User) user).setClient(client);
			((User) user).setVersion(ProxyRequest.getVersion(appId));
		}
		super.update(user);
		req.sessionUser(user);
		if (user.hasChangeLanguage() || user.isAdmin()) {
			req.sessionContext().setLanguage(user.getLanguage());
		}
		// 新增登录日志
		saveLoginLog(new LoginLogEntry(form.username, user.getId(), client, loginWay, now, loginIp, null));
		loginCallback(user, now);
		return msger;
	}

	protected void loginCallback(T member, Date now) {
	}

	protected void prepareInviteCode(boolean addOrEdit, T input) {
		if (addOrEdit) {
			int retry = 10;
			String inviteCode;
			do {
				inviteCode = UserFieldAutoGenerator.makeInviteCode(input);
			} while (getByInviteCode(inviteCode) != null && retry-- > 0);
			input.setInviteCode(inviteCode);
		}
	}

	/**
	 * 根据用户唯一凭据获取对应的用户
	 *
	 * @param credential 用户名/邮箱/手机号码
	 * @param type 类型
	 * @param unique 是否全局唯一（注销号之后无法重复创建）
	 */
	public T getByCredential(String credential, @Nullable CredentialType type, boolean unique) {
		if (StringUtil.isEmpty(credential)) {
			return null;
		}
		if (type == null) {
			type = CredentialType.identify(credential);
		}
		QueryWrapper<T> wrapper = new QueryWrapper<T>()
				.ge(type.statusField, type.statusValue);
		if (!unique) {
			wrapper.gt(MemberContent.STATUS, UserStatus.INVALID.value);
		}
		if (type == CredentialType.PHONE && PhoneArea.VN.has(credential)) {
			// 越南地区手机号码可能存在 前置 0，两种情况都要覆盖
			String other = PhoneArea.VN.clip(credential);
			if (other.equals(credential)) { // 如果原始输入没有前置 0，则 other 需要前置 0
				other = credential.replace("-", "-0");
			}
			wrapper.in(type.field, credential, other);
			final List<T> list = super.selectList(wrapper);
			switch (list.size()) {
				case 0:
					return null;
				case 1:
					return list.get(0);
				default:
					return CollectionUtil.findFirst(list, t -> credential.equals(t.getPhone()));
			}
		} else {
			wrapper.eq(type.field, credential);
			return super.selectOne(wrapper);
		}
	}

	public T getByCredential(String credential, @Nullable CredentialType type) {
		return getByCredential(credential, type, false);
	}

	/**
	 * 根据 邀请码 查询对应的邀请用户
	 */
	public T getByInviteCode(String inviteCode) {
		return super.selectOne(new QueryWrapper<T>().eq("invite_code", inviteCode));
	}

	/** 密码正则表达式8-16位必须包含字母大小写、数字、特殊符号3种组合 */
	static final Pattern pattern = Pattern.compile("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,16}$");

	/**
	 * 修改登录密码
	 *
	 * @param currentUser 当前用户
	 * @param oldPwd 当前登录密码
	 * @param newPwd 新登录密码
	 * @param modifyOrReset 【如果该参数为false，则表示无需校验当前登录密码，直接重置为新登录密码】
	 */
	public Messager<T> changePwd(T currentUser, String oldPwd, String newPwd, boolean modifyOrReset) {
		if (modifyOrReset && StringUtils.isEmpty(oldPwd) || StringUtils.isEmpty(newPwd)) {
			return Messager.error(I18N.msg(UserI18nKey.CHANGE_PWD_DATA_ERROR));
		}
		if (currentUser instanceof User) {
			I18N.assertTrue(Validators.matchPassword(newPwd), UserI18nKey.PASSWORD_FORMAT_SET_ERROR);
		} else {
			final Matcher matcher = pattern.matcher(newPwd);
			I18N.assertTrue(matcher.matches(), UserI18nKey.PASSWORD_FORMAT_SET_ERROR);
		}
		if (modifyOrReset) {
			if (oldPwd.equals(newPwd)) {
				return Messager.error(I18N.msg(UserI18nKey.CHANGE_PWD_SAME));
			}
			currentUser = get(currentUser.getId());
			if (!currentUser.checkPassword(oldPwd, true)) {
				return Messager.error(I18N.msg(UserI18nKey.USER_PASSWORD_ERROR_RESUME_LOAD));
			}
		}
		currentUser.setPassword(newPwd, true);
		update(currentUser);
		return Messager.hideData(currentUser);
	}

}
